﻿ module AirSimulation

  open UM
  open Math
  open Coroutines
  open System
  open Microsoft.Xna.Framework
  open Microsoft.Xna.Framework.Input
  open Microsoft.FSharp.Core.LanguagePrimitives //per usare Float32WithMeasure
  open Microsoft.Xna.Framework.Graphics

  //TUTTI I TIPI UTILIZZATI
  type GameState =
    {
        Aerei : Variable<List<Aereo>>
        Proiettili : Variable<List<Proiettile>>
        mutable Points : float32
        mutable NumProiettili : float32
        mutable EspX : float32<m>
        mutable EspY : float32<m>
        mutable Level : float32
        mutable VminAerei : float32
        mutable VmaxAerei : int
    }
   and Aereo = {
       PosA : Variable<Vector2<m>>; 
       VelA : Variable<float32<m/s>>;
       Move : bool //stabilisce il movimento curvilineo che esiste di 2 tipi(andare verso l'alto o verso il basso)
       BoundA : Variable<BoundingBox>
       ColliderA : Variable<List<Proiettile>> }
   and Proiettile = {
       PosP : Variable<Vector2<m>>;
       Direz : Vector2<m>;    
       VelP : Variable<float32<m/s>>;
       BoundP : Variable<BoundingSphere>
       ColliderP : Variable<List<Aereo>> }
    //GAME STATE
  let mutable statechange =
    {
       Aerei = Variable(fun () -> [])
       Proiettili = Variable(fun () -> [])
       Points = 0.0f
       NumProiettili = 40.0f
       EspX = -1000.0f<m>
       EspY = -1000.0f<m>
       Level = 0.0f
       VminAerei = 80.0f
       VmaxAerei = 200
    }
  //INIZIALIZZO VARIABILI GRANDEZZA PANNELLO + le dimensioni del modello aereo
  let mutable PanelWidthMax = 0.0f<m>
  let mutable PanelHeightMax = 0.0f<m>
  let mutable PanelWidthMin = 0.0f<m>
  let mutable PanelHeightMin = 0.0f<m>
  let resolution (width_max:float32,height_max:float32,width_mix:float32,height_mix:float32) =
        do PanelWidthMax<- (width_max |>Float32WithMeasure :float32<m>)
        do PanelHeightMax<- (height_max |>Float32WithMeasure :float32<m>)
        do PanelWidthMin<- (width_mix |>Float32WithMeasure :float32<m>)
        do PanelHeightMin<- (height_mix |>Float32WithMeasure :float32<m>)
  //INIZIALIZZO BOUNDING SPHERE
  let mutable IaereoBB = new BoundingBox(new Vector3(0.0f, 0.0f,0.0f),new Vector3(0.0f, 0.0f,0.0f))
  let mutable AaereoRaggio = 0.0f<m> 
  let Aereoaltezza = 13.0f<m>
  let mutable IProiettileBS = new BoundingSphere(new Vector3(0.0f, 0.0f,0.0f),0.0f)
  let inizializationBSphere (BBA:BoundingBox,BSP:BoundingSphere,Araggio:float32<m>) =
         do IaereoBB <- BBA
         do IProiettileBS <- BSP
         do AaereoRaggio <- Araggio
  //AGGIORNO LIVELLO
  let private Livello1 () = 
         do statechange <- {
               Aerei = Variable(fun () -> [])
               Proiettili = Variable(fun () -> [])
               Points = 0.0f
               NumProiettili = 30.0f
               EspX = -1000.0f<m>
               EspY = -1000.0f<m>
               Level = 1.0f
               VminAerei = 120.0f
               VmaxAerei = 200
            }
  let private Livello2 () = 
           do statechange <- {
               Aerei = Variable(fun () -> [])
               Proiettili = Variable(fun () -> [])
               Points = 0.0f
               NumProiettili = 25.0f
               EspX = -1000.0f<m>
               EspY = -1000.0f<m>
               Level = 2.0f
               VminAerei = 160.0f
               VmaxAerei = 200
            }
  let private Livello3 () = 
        do statechange <- {
            Aerei = Variable(fun () -> [])
            Proiettili = Variable(fun () -> [])
            Points = 0.0f
            NumProiettili = 18.0f
            EspX = -1000.0f<m>
            EspY = -1000.0f<m>
            Level = 3.0f
            VminAerei = 180.0f
            VmaxAerei = 200
        }
  let public Azzera () = 
        do statechange <- {
           Aerei = Variable(fun () -> [])
           Proiettili = Variable(fun () -> [])
           Points = 0.0f
           NumProiettili = 40.0f
           EspX = -1000.0f<m>
           EspY = -1000.0f<m>
           Level = 0.0f
           VminAerei = 80.0f
           VmaxAerei = 200
        }

  //gli aerei nei livelli spureriori allo zero si muovono seguendo la tangente iperbolica
  let mutable ValS1=0.0f
  //let mutable ValS2=0.6f  old con il sin
  let mutable ValS2=0.0f

  //AGGIORNO STATO DI GIOCO
  let rec update_state(dt:float32<s>) =
    if (statechange.Points >14.0f)then
        if (statechange.Level = 3.0f) then
            do statechange.Level <- 4.0f;
        if (statechange.Level = 2.0f) then
            do Livello3() 
        if (statechange.Level = 1.0f) then
            do Livello2() 
        if(statechange.Level = 0.0f) then
            do Livello1()
        
        
    for a in !statechange.Aerei do
      updateaerei a dt
    //creo la nuova lista di aerei(eliminando quelli fuori schermo e quelli che si sono schiantati)
    statechange.Aerei := 
      [for a in !statechange.Aerei do  //il +PanelWidthMin sotto si riferisce alla lunghezza dell'aereo, infatti se non si inserisce questo valore, l'aereo procede x una sua lunghezza ulteriore oltre oltre il confine
        if (!a.ColliderA).Length = 0 then
            if (((!a.PosA).X > (PanelWidthMin) && !a.VelA < 0.0f<m/s>) ||((!a.PosA).X < (PanelWidthMax + PanelWidthMin) && !a.VelA > 0.0f<m/s>)) then
                yield a
        else
            //aggiorno punteggi
            do statechange.Points <- statechange.Points + 1.0f
            //imposto i valori di x e y per l'animazione dell'esplosione
            do statechange.EspX <- (!a.PosA).X
            do statechange.EspY <- (!a.PosA).Y
         ]
    for p in !statechange.Proiettili do
        updateproiettile p dt
    statechange.Proiettili := 
        [for p in !statechange.Proiettili do
        if (!p.ColliderP).Length = 0 && ((!p.PosP).X > PanelWidthMin && (!p.PosP).X < PanelWidthMax && (!p.PosP).Y > PanelHeightMin && (!p.PosP).Y < PanelHeightMax) then
            yield p]
  and private updateaerei (a:Aereo) (dt:float32<s>) =
   if (statechange.Level > 0.0f) then
//old alternativa
//        do ValS1<- ValS1+0.0006f
//        do ValS2<- ValS2-0.0006f
//        a.PosA := !a.PosA + new Vector2<m>(dt * a.VelA.Value, (0.25 |> float32) * (Math.Sin( rndValSin(a.VelA.Value) |> float) |> float32 |>Float32WithMeasure :float32<m>))
            if(a.Move = true) then
                do ValS1<- ValS1+0.002f
                a.PosA := !a.PosA + new Vector2<m>(dt * a.VelA.Value, (0.35 |> float32) *(Math.Tanh( ValS1|> float) |> float32 |>Float32WithMeasure :float32<m>))
            else
                do ValS2<- ValS2-0.002f
                a.PosA := !a.PosA + new Vector2<m>(dt * a.VelA.Value, (0.35 |> float32) *(Math.Tanh( ValS2|> float) |> float32 |>Float32WithMeasure :float32<m>))
   else
            a.PosA := !a.PosA + new Vector2<m>(dt * a.VelA.Value,0.0f<m>)  //il -13 sulla x minima eil -18 sulla x max servon per regolare in modo più preciso la grandezza delal bounding box che altrienti sarebbe troppo grande
   a.BoundA := new BoundingBox(new Vector3((!a.PosA).X - (AaereoRaggio - 13.0f<m>) |> float32,(!a.PosA).Y - Aereoaltezza |> float32, 0.0f),new Vector3((!a.PosA).X + (AaereoRaggio - 18.0f<m>) |> float32,(!a.PosA).Y + Aereoaltezza |> float32, 0.0f))
   a.ColliderA:= [for p in !statechange.Proiettili do
                        if(!p.BoundP).Intersects(!a.BoundA) then
                            yield p
                        ]
  and private updateproiettile (p:Proiettile) (dt:float32<s>) =
    p.PosP := !p.PosP + p.Direz * new Vector2<1>(dt * p.VelP.Value |> float32,dt * p.VelP.Value |> float32)
    p.BoundP := new BoundingSphere(new Vector3((!p.PosP).X |> float32,(!p.PosP).Y |> float32, 0.0f),IProiettileBS.Radius)
    p.ColliderP:= [ for a in !statechange.Aerei do
                        if(!a.BoundA).Intersects(!p.BoundP) then
                              yield a
                         ]

  let private (!) = immediate_read
  let private (:=) = immediate_write

  //FUNZIONI UTILI

 //  let private rndstart =
//      do rd<-random.Next(0,1 ) //non funziona rimane sempre a zero xkè il random next 0 1 non và
  //RANDOM SPAWN AEREI
  let mutable rd=random.Next(0,1 )
  let private rndaerei (rd:int) : float32 =
     if rd=0 then
       1.0f
     else
       -1.0f
  let private rndMove (rd:int): bool =
     if rd=0 then
       true
     else
       false
  //POSIZIONE SASSI IN BASE ALLA RISOLUZIONE
  let private SassoX (): float32<m>=
        if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 800.0f<m> && (PanelHeightMax + PanelHeightMin)= 600.0f<m>)then 
             (((PanelWidthMax + PanelWidthMin)/5.5f |> float) + (153.0f |> float) * System.Math.Atan2((500.0f |> float),((InputGame.InputManagerGame.GetState().mouseX |> float) - ((PanelWidthMax + PanelWidthMin)/2.0f<m> |> float))) |> float32 |>Float32WithMeasure :float32<m>)
        else 
            if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 1024.0f<m> && (PanelHeightMax + PanelHeightMin)= 768.0f<m>)then
                    (((PanelWidthMax + PanelWidthMin)/5.2f |> float) + (185.0f |> float) * System.Math.Atan2((500.0f |> float),((InputGame.InputManagerGame.GetState().mouseX |> float) - ((PanelWidthMax + PanelWidthMin)/2.0f<m> |> float))) |> float32 |>Float32WithMeasure :float32<m>)
            else
                    (((PanelWidthMax + PanelWidthMin)/4.8f |> float) + (220.0f |> float) * System.Math.Atan2((500.0f |> float),((InputGame.InputManagerGame.GetState().mouseX |> float) - ((PanelWidthMax + PanelWidthMin)/2.0f<m> |> float))) |> float32 |>Float32WithMeasure :float32<m>)
  let private SassoY (): float32<m> =
      if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 800.0f<m> && (PanelHeightMax + PanelHeightMin)= 600.0f<m>)then 
            ((PanelHeightMax + PanelHeightMin) - (PanelHeightMax + PanelHeightMin)/5.2f)
      else
            if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 1024.0f<m> && (PanelHeightMax + PanelHeightMin)= 768.0f<m>)then      
                    ((PanelHeightMax + PanelHeightMin) - (PanelHeightMax + PanelHeightMin)/5.4f)
            else
                    ((PanelHeightMax + PanelHeightMin) - (PanelHeightMax + PanelHeightMin)/5.1f)
//DIREZIONE SASSI IN BASE ALLA RISOLUZIONE
  let private SassoDirX (): float32=
        if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 800.0f<m> && (PanelHeightMax + PanelHeightMin)= 600.0f<m>)then 
             ((InputGame.InputManagerGame.GetState().mouseX |> float32)- (((PanelWidthMax + PanelWidthMin)/5.5f |> float) + (153.0f |> float) * System.Math.Atan2((500.0f |> float),((InputGame.InputManagerGame.GetState().mouseX |> float) - ((PanelWidthMax + PanelWidthMin)/2.0f<m> |> float))) |> float32) )
        else 
            if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 1024.0f<m> && (PanelHeightMax + PanelHeightMin)= 768.0f<m>)then
                    ((InputGame.InputManagerGame.GetState().mouseX |> float32)- (((PanelWidthMax + PanelWidthMin)/5.2f |> float) + (185.0f |> float) * System.Math.Atan2((500.0f |> float),((InputGame.InputManagerGame.GetState().mouseX |> float) - ((PanelWidthMax + PanelWidthMin)/2.0f<m> |> float))) |> float32) )
            else
                    ((InputGame.InputManagerGame.GetState().mouseX |> float32)- (((PanelWidthMax + PanelWidthMin)/4.8f |> float) + (220.0f |> float) * System.Math.Atan2((500.0f |> float),((InputGame.InputManagerGame.GetState().mouseX |> float) - ((PanelWidthMax + PanelWidthMin)/2.0f<m> |> float))) |> float32) )
  let private SassoDirY (): float32 =
      if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 800.0f<m> && (PanelHeightMax + PanelHeightMin)= 600.0f<m>)then 
            ((InputGame.InputManagerGame.GetState().mouseY |> float32)-((PanelHeightMax + PanelHeightMin |> float32)- (PanelHeightMax + PanelHeightMin)/5.2f<m>) |> float32)
      else
            if((PanelWidthMax + PanelWidthMin + PanelWidthMin) = 1024.0f<m> && (PanelHeightMax + PanelHeightMin)= 768.0f<m>)then      
                    ((InputGame.InputManagerGame.GetState().mouseY |> float32)-((PanelHeightMax + PanelHeightMin |> float32)- (PanelHeightMax + PanelHeightMin)/5.4f<m>) |> float32)
            else
                    ((InputGame.InputManagerGame.GetState().mouseY |> float32)-((PanelHeightMax + PanelHeightMin |> float32)- (PanelHeightMax + PanelHeightMin)/5.1f<m>) |> float32)

  //  MAIN
  let mutable private main =
    let spawn_aerei() =
      co{
        do! wait (random.Next(2,3) |> float)
        do rd<-random.NextFloat+0.5f |>int //grazie al cast ad int viene posto ad 1 il nextfloat compreso tra 0.5 ed 1, viene posto ad 0 il nextfloat tra 0 e 0.5
        statechange.Aerei := 
          {
            PosA =    
                let x=(PanelWidthMin|>int) + rd * ((PanelWidthMax)|>int) |> float32 |>Float32WithMeasure :float32<m>
                //casuale y
                let y=random.Next(-(PanelHeightMin |>int)/4,  (PanelHeightMax|>int) - ((AaereoRaggio * 6.2f)|>int) ) |> float32 |>Float32WithMeasure :float32<m>
                //casuale y ma ad ntervalli costanti
                //let y= -(PanelHeightMin |>int)/4 + random.Next(1, 5) * (AaereoRaggio|>int) |> float32 |>Float32WithMeasure :float32<m>
                variable(Vector2<m>(x,y))
            VelA =
            //prima del + c'è la velocità minima che un aereo ha, dopo del + c'è la velocità propria dell'aereo, e il max è il valore max che può assumere random next
                let v= statechange.VminAerei *rndaerei(rd) + (rndaerei(rd)*(random.Next(1,statechange.VmaxAerei)|>float32)) |> float32 |>Float32WithMeasure :float32<m/s>
                variable(v)
            Move = 
                do rd<-random.NextFloat+0.5f |>int
                rndMove (rd)
            //inizializzo la bounding sphere in una posizione molto distante del centro di gioco
            BoundA = variable(IaereoBB)
            ColliderA = Variable(fun () -> [])
          } :: !statechange.Aerei
        return()
      }
    let spara_proiettili() = 
        co{
        //chiama la couroutines  yield che si ferma per ogni fotogramma, quindi si ferma qui la couroutine generale, viene chiamato
        //l'update e il draw di c # che mi creerà un altro fotogramma il quale mi farà riprendere l'esecuzione da qui
          do! yield_
          if statechange.NumProiettili > 0.0f then
              if InputGame.InputManagerGame.GetState().click then
                do statechange.NumProiettili <- statechange.NumProiettili - 1.0f
                statechange.Proiettili :=
                  {                 // 800x 600 in x divido per 5.5 e sommo 153........in y divido per 5.2
                                   // 1024x 768 in x divido per 5.2 e sommo 185........in y divido per 5.4
                                   // 1280x 1024 in x divido per 4.8 e sommo 220........in y divido per 5.1
                  //PosP è già in float32<m>
                    PosP = 
                      let x=SassoX()
                      let y=SassoY()
                      variable(Vector2<m>(x,y))
                  //calcolo il vettore direzione attraverso la sotrazione di 2 punti, punto cliccato e posizione di partenza
                    Direz =         // 800x 600 in x divido per 5.5 e sommo 153........in y divido per 5.2
                                    // 1024x 768 in x divido per 5.2 e sommo 185........in y divido per 5.4
                                    // 1280x 1024 in x divido per 4.8 e sommo 220........in y divido per 5.1
                      let x =SassoDirX()
                      let y =SassoDirY()
                   //divido per la radice di x^2+y^2 per normalizzare
                      Vector2<m>(((x/sqrt((x*x)+(y*y))) |>Float32WithMeasure :float32<m>) ,((y/sqrt((x*x)+(y*y))) |>Float32WithMeasure :float32<m>))
                    VelP = 
                      let x = 350.0f |>Float32WithMeasure :float32<m/s>
                      variable(x)
                       //inizializzo la bounding sphere in una posizione molto distante del centro di gioco
                    BoundP = variable(IProiettileBS)
                    ColliderP = Variable(fun () -> [])
                  } :: !statechange.Proiettili
          return() 
        }
    ( repeat_ (spara_proiettili()) .||> repeat_ (spawn_aerei()))
    
  let update_script() = main <- update_ai main